#pragma once

#ifndef __CEBMPBUTTON_H__
#define __CEBMPBUTTON_H__
// bitmap button extended styles
#define BMPBTN_HOVER			0x00000001
#define BMPBTN_AUTO3D_SINGLE	0x00000002
#define BMPBTN_AUTO3D_DOUBLE	0x00000004
#define BMPBTN_AUTOSIZE			0x00000008
#define BMPBTN_SHAREIMAGELISTS	0x00000010
#define BMPBTN_AUTOFIRE			0x00000020

#pragma warning(disable : 4996)


namespace WTL
{
	class CFontEx : public CFont
	{
	public:
		HFONT CreateFont(int nHeight, int nWidth, int nEscapement,
						 int nOrientation, int nWeight, BYTE bItalic, BYTE bUnderline,
						 BYTE cStrikeOut, BYTE nCharSet, BYTE nOutPrecision,
						 BYTE nClipPrecision, BYTE nQuality, BYTE nPitchAndFamily,
						 LPCTSTR lpszFacename)
		{
			ATLASSERT(m_hFont == NULL);
			LOGFONT logFont;
			logFont.lfHeight = nHeight;
			logFont.lfWidth = nWidth;
			logFont.lfEscapement = nEscapement;
			logFont.lfOrientation = nOrientation;
			logFont.lfWeight = nWeight;
			logFont.lfItalic = bItalic;
			logFont.lfUnderline = bUnderline;
			logFont.lfStrikeOut = cStrikeOut;
			logFont.lfCharSet = nCharSet;
			logFont.lfOutPrecision = nOutPrecision;
			logFont.lfClipPrecision = nClipPrecision;
			logFont.lfQuality = nQuality;
			logFont.lfPitchAndFamily = nPitchAndFamily;
			lstrcpyn(logFont.lfFaceName, lpszFacename, _tcslen(lpszFacename));

			m_hFont = ::CreateFontIndirect(&logFont);

			return m_hFont;
		}
	};

	template < class T, class TBase = CButton, class TWinTraits = ATL::CControlWinTraits >
	class ATL_NO_VTABLE CBMPButtonImpl : public ATL::CWindowImpl< T, TBase, TWinTraits>
	{
	public:
		DECLARE_WND_SUPERCLASS(NULL, TBase::GetWndClassName())

		enum
		{
			_nImageNormal = 0,
			_nImagePushed,
			_nImageFocusOrHover,
			_nImageDisabled,
			_nImageCount = 4,
		};

		enum
		{
			ID_TIMER_FIRST = 1000,
			ID_TIMER_REPEAT = 1001
		};

		// Bitmap button specific extended styles
		DWORD m_dwExtendedStyle;

		CImageList m_ImageList;
		int m_nImage[_nImageCount];

		// Internal states
		unsigned m_fMouseOver:1;
		unsigned m_fFocus:1;
		unsigned m_fPressed:1;


		// Constructor/Destructor
		CBMPButtonImpl(DWORD dwExtendedStyle = BMPBTN_AUTOSIZE, HIMAGELIST hImageList = NULL) :
				m_ImageList(hImageList), m_dwExtendedStyle(dwExtendedStyle),
				m_fMouseOver(0), m_fFocus(0), m_fPressed(0)
		{
			m_nImage[_nImageNormal] = -1;
			m_nImage[_nImagePushed] = -1;
			m_nImage[_nImageFocusOrHover] = -1;
			m_nImage[_nImageDisabled] = -1;

			m_Font.CreateFont(16, 0, 0, 0, FW_BOLD, 0, 0, 0, DEFAULT_CHARSET,
							  OUT_DEFAULT_PRECIS,
							  CLIP_DEFAULT_PRECIS,
							  DEFAULT_QUALITY,
							  DEFAULT_PITCH | FF_SWISS, L"Tahoma");
			m_nVerticalAdj = 0;
		}

		~CBMPButtonImpl()
		{
			if ((m_dwExtendedStyle & BMPBTN_SHAREIMAGELISTS) == 0)
			{
				m_ImageList.Destroy();
			}
		}

		// overridden to provide proper initialization
		BOOL SubclassWindow(HWND hWnd)
		{
#if (_MSC_VER >= 1300)
			BOOL bRet = ATL::CWindowImpl< T, TBase, TWinTraits>::SubclassWindow(hWnd);
#else // !(_MSC_VER >= 1300)
			typedef ATL::CWindowImpl< T, TBase, TWinTraits>   _baseClass;
			BOOL bRet = _baseClass::SubclassWindow(hWnd);
#endif // !(_MSC_VER >= 1300)
			if (bRet)
			{
				Init();
			}
			return bRet;
		}

		// Attributes
		DWORD GetBitmapButtonExtendedStyle() const
		{
			return m_dwExtendedStyle;
		}

		DWORD SetBitmapButtonExtendedStyle(DWORD dwExtendedStyle, DWORD dwMask = 0)
		{
			DWORD dwPrevStyle = m_dwExtendedStyle;
			if (dwMask == 0)
			{
				m_dwExtendedStyle = dwExtendedStyle;
			}
			else
			{
				m_dwExtendedStyle = (m_dwExtendedStyle & ~dwMask) | (dwExtendedStyle & dwMask);
			}

			return dwPrevStyle;
		}

		HIMAGELIST GetImageList() const
		{
			return m_ImageList;
		}

		HIMAGELIST SetImageList(HIMAGELIST hImageList)
		{
			HIMAGELIST hImageListPrev = m_ImageList;
			m_ImageList = hImageList;
			if ((m_dwExtendedStyle & BMPBTN_AUTOSIZE) != 0 && ::IsWindow(m_hWnd))
			{
				SizeToImage();
			}
			return hImageListPrev;
		}

		int GetToolTipTextLength() const
		{
			return (m_lpstrToolTipText == NULL) ? -1 : lstrlen(m_lpstrToolTipText);
		}

		bool GetToolTipText(LPTSTR lpstrText, int nLength) const
		{
			ATLASSERT(lpstrText != NULL);
			if (m_lpstrToolTipText == NULL)
			{
				return false;
			}

			return (lstrcpyn(lpstrText, m_lpstrToolTipText, min(nLength, lstrlen(m_lpstrToolTipText) + 1)) != NULL);
		}

		// Operations
		void SetImages(int nNormal, int nPushed = -1, int nFocusOrHover = -1, int nDisabled = -1)
		{
			if (nNormal != -1)
			{
				m_nImage[_nImageNormal] = nNormal;
			}
			if (nPushed != -1)
			{
				m_nImage[_nImagePushed] = nPushed;
			}
			if (nFocusOrHover != -1)
			{
				m_nImage[_nImageFocusOrHover] = nFocusOrHover;
			}
			if (nDisabled != -1)
			{
				m_nImage[_nImageDisabled] = nDisabled;
			}
		}

		BOOL ResizeClient(int nWidth, int nHeight)
		{
			ATLASSERT(::IsWindow(m_hWnd));

			RECT rcWnd;
			if (!GetClientRect(&rcWnd))
			{
				return FALSE;
			}

			if (nWidth != -1)
			{
				rcWnd.right = nWidth;
			}
			if (nHeight != -1)
			{
				rcWnd.bottom = nHeight;
			}

			if (!::AdjustWindowRectEx(&rcWnd,
									  GetStyle(),
									  (!(GetStyle() & WS_CHILD) && FALSE),
									  GetExStyle()))
			{
				return FALSE;
			}

			UINT uFlags = SWP_NOZORDER | SWP_NOMOVE;

			return SetWindowPos(NULL, 0, 0, rcWnd.right - rcWnd.left, rcWnd.bottom - rcWnd.top, uFlags);
		}

		BOOL SizeToImage()
		{
			ATLASSERT(::IsWindow(m_hWnd) && m_ImageList.m_hImageList != NULL);
			int cx = 0;
			int cy = 0;
			BOOL bResult = FALSE;

			if (m_ImageList.GetIconSize(cx, cy))
			{
				bResult = ResizeClient(cx, cy);
			}

			return bResult;
		}

		// Override ables
		void DoPaint(CDCHandle dc)
		{
			ATLASSERT(m_ImageList.m_hImageList != NULL);   // image list must be set
			ATLASSERT(m_nImage[0] != -1);                  // main bitmap must be set

			// set bitmap according to the current button state
			int nImage = -1;
			bool bHover = IsHoverMode();

			if (m_fPressed == 1)
			{
				nImage = m_nImage[_nImagePushed];
			}
			else if ((!bHover && m_fFocus == 1) || (bHover && m_fMouseOver == 1))
			{
				nImage = m_nImage[_nImageFocusOrHover];
			}
			else if (!IsWindowEnabled())
			{
				nImage = m_nImage[_nImageDisabled];
			}

			if (nImage == -1)  // not there, use default one
			{
				nImage = m_nImage[_nImageNormal];
			}

			// draw the button image
			int xyPos = 0;
			if ((m_fPressed == 1) && ((m_dwExtendedStyle & (BMPBTN_AUTO3D_SINGLE | BMPBTN_AUTO3D_DOUBLE)) != 0) && (m_nImage[_nImagePushed] == -1))
			{
				xyPos = 1;
			}

			RECT rect;
			GetClientRect(&rect);

			dc.FillRect(&rect, (HBRUSH)GetStockObject(LTGRAY_BRUSH));

			m_ImageList.Draw(dc, nImage, xyPos, xyPos, ILD_NORMAL);

			// draw 3D border if required
			if ((m_dwExtendedStyle & (BMPBTN_AUTO3D_SINGLE | BMPBTN_AUTO3D_DOUBLE)) != 0)
			{
				if (m_fPressed == 1)
				{
					dc.DrawEdge(&rect,
								((m_dwExtendedStyle & BMPBTN_AUTO3D_SINGLE) != 0) ? BDR_SUNKENOUTER : EDGE_SUNKEN,
								BF_RECT);
				}
				else if (!bHover || m_fMouseOver == 1)
				{
					dc.DrawEdge(&rect,
								((m_dwExtendedStyle & BMPBTN_AUTO3D_SINGLE) != 0) ? BDR_RAISEDINNER : EDGE_RAISED,
								BF_RECT);
				}

				if (!bHover && m_fFocus == 1)
				{
					::InflateRect(&rect,
								  -2 * ::GetSystemMetrics(SM_CXEDGE),
								  -2 * ::GetSystemMetrics(SM_CYEDGE));
					dc.DrawFocusRect(&rect);
				}
			}

			rect.top += m_nVerticalAdj;
			rect.bottom += m_nVerticalAdj;

			TCHAR szBtnText[255];
			::GetWindowText(m_hWnd, szBtnText, 255);

			int nPrevBkMode = dc.GetBkMode();
			dc.SetBkMode(TRANSPARENT);
			dc.SetTextColor(RGB(255, 255, 255));

			HFONT pOldFont = dc.SelectFont(m_Font);

			dc.DrawText(szBtnText, _tcslen(szBtnText), &rect, DT_CENTER | DT_VCENTER);

			dc.SelectFont(pOldFont);
			dc.SetBkMode(nPrevBkMode);
		}

		// Message map and handlers
		BEGIN_MSG_MAP(CBMPButtonImpl)
			MESSAGE_HANDLER(WM_CREATE, OnCreate)
			MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
			MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground)
			MESSAGE_HANDLER(WM_PAINT, OnPaint)
			MESSAGE_HANDLER(WM_SETFOCUS, OnFocus)
			MESSAGE_HANDLER(WM_KILLFOCUS, OnFocus)
			MESSAGE_HANDLER(WM_LBUTTONDOWN, OnLButtonDown)
			MESSAGE_HANDLER(WM_LBUTTONDBLCLK, OnLButtonDblClk)
			MESSAGE_HANDLER(WM_LBUTTONUP, OnLButtonUp)
			MESSAGE_HANDLER(WM_CAPTURECHANGED, OnCaptureChanged)
			MESSAGE_HANDLER(WM_ENABLE, OnEnable)
			MESSAGE_HANDLER(WM_KEYDOWN, OnKeyDown)
			MESSAGE_HANDLER(WM_KEYUP, OnKeyUp)
			MESSAGE_HANDLER(WM_TIMER, OnTimer)
		END_MSG_MAP()

		LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
		{
			Init();
			bHandled = FALSE;

			return 1;
		}

		LRESULT OnDestroy(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
		{
			bHandled = FALSE;

			return 1;
		}

		LRESULT OnEraseBackground(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
		{
			return 1;   // no background needed
		}

		LRESULT OnPaint(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/)
		{
			T* pT = static_cast<T*>(this);

			if (wParam != NULL)
			{
				pT->DoPaint((HDC)wParam);
			}
			else
			{
				CPaintDC dc(m_hWnd);
				pT->DoPaint(dc.m_hDC);
			}

			return 0;
		}

		LRESULT OnFocus(UINT uMsg, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
		{
			m_fFocus = (uMsg == WM_SETFOCUS) ? 1 : 0;
			Invalidate();
			UpdateWindow();
			bHandled = FALSE;

			return 1;
		}

		LRESULT OnLButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
		{
			LRESULT lRet = 0;
			if (IsHoverMode())
			{
				SetCapture();
			}
			else
			{
				lRet = DefWindowProc(uMsg, wParam, lParam);
			}

			if (::GetCapture() == m_hWnd)
			{
				m_fPressed = 1;
				Invalidate();
				UpdateWindow();
			}

			if ((m_dwExtendedStyle & BMPBTN_AUTOFIRE) != 0)
			{
				int nElapse = 250;
				SetTimer(ID_TIMER_FIRST, nElapse);
			}

			return lRet;
		}

		LRESULT OnLButtonDblClk(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
		{
			LRESULT lRet = 0;

			if (!IsHoverMode())
			{
				lRet = DefWindowProc(uMsg, wParam, lParam);
			}

			if (::GetCapture() != m_hWnd)
			{
				SetCapture();
			}

			if (m_fPressed == 0)
			{
				m_fPressed = 1;
				Invalidate();
				UpdateWindow();
			}

			return lRet;
		}

		LRESULT OnLButtonUp(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
		{
			LRESULT lRet = 0;
			bool bHover = IsHoverMode();

			if (!bHover)
			{
				lRet = DefWindowProc(uMsg, wParam, lParam);
			}

			if (::GetCapture() == m_hWnd)
			{
				if (bHover && m_fPressed == 1)
				{
					::SendMessage(GetParent(),
								  WM_COMMAND,
								  MAKEWPARAM(GetDlgCtrlID(), BN_CLICKED),
								  (LPARAM)m_hWnd);
				}

				::ReleaseCapture();
			}

			return lRet;
		}

		LRESULT OnCaptureChanged(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
		{
			if (m_fPressed == 1)
			{
				m_fPressed = 0;
				Invalidate();
				UpdateWindow();
			}
			bHandled = FALSE;
			return 1;
		}

		LRESULT OnEnable(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
		{
			Invalidate();
			UpdateWindow();
			bHandled = FALSE;
			return 1;
		}


		LRESULT OnKeyDown(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled)
		{
			if (wParam == VK_SPACE && IsHoverMode())
			{
				return 0;   // ignore if in hover mode
			}

			if (wParam == VK_SPACE && m_fPressed == 0)
			{
				m_fPressed = 1;
				Invalidate();
				UpdateWindow();
			}
			bHandled = FALSE;

			return 1;
		}

		LRESULT OnKeyUp(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& bHandled)
		{
			if (wParam == VK_SPACE && IsHoverMode())
			{
				return 0;   // ignore if in hover mode
			}

			if (wParam == VK_SPACE && m_fPressed == 1)
			{
				m_fPressed = 0;
				Invalidate();
				UpdateWindow();
			}

			bHandled = FALSE;

			return 1;
		}

		LRESULT OnTimer(UINT /*uMsg*/, WPARAM wParam, LPARAM /*lParam*/, BOOL& /*bHandled*/)
		{
			ATLASSERT((m_dwExtendedStyle & BMPBTN_AUTOFIRE) != 0);
			switch (wParam)  // timer ID
			{
			case ID_TIMER_FIRST:
				KillTimer(ID_TIMER_FIRST);
				if (m_fPressed == 1)
				{
					::SendMessage(GetParent(), WM_COMMAND, MAKEWPARAM(GetDlgCtrlID(), BN_CLICKED), (LPARAM)m_hWnd);
					const int nElapse = 250;
					SetTimer(ID_TIMER_REPEAT, nElapse);
				}
				break;

			case ID_TIMER_REPEAT:
				if (m_fPressed == 1)
				{
					::SendMessage(GetParent(),
								  WM_COMMAND,
								  MAKEWPARAM(GetDlgCtrlID(), BN_CLICKED),
								  (LPARAM)m_hWnd);
				}
				else if (::GetCapture() != m_hWnd)
				{
					KillTimer(ID_TIMER_REPEAT);
				}
				break;

			default:	// not our timer
				break;
			}
			return 0;
		}

		// Implementation
		void Init()
		{
			// We need this style to prevent Windows from painting the button
			ModifyStyle(0, BS_OWNERDRAW);

			// create a tool tip
			if (m_ImageList.m_hImageList != NULL && (m_dwExtendedStyle & BMPBTN_AUTOSIZE) != 0)
			{
				SizeToImage();
			}
		}

		bool IsHoverMode() const
		{
			return ((m_dwExtendedStyle & BMPBTN_HOVER) != 0);
		}

		BOOL AttachToDlgItem(HWND hwndParent, UINT nDlgID)
		{
			m_nDlgItem = nDlgID;
			m_hwndParent = hwndParent;

			return SubclassWindow(::GetDlgItem(hwndParent, nDlgID));
		}

		void SetFont(CFont* pFont, BOOL bRedraw = TRUE)
		{
			CButton::SetFont(pFont, bRedraw);
			LOGFONT LogFont;
			if ( m_pFont && pFont->GetLogFont(&LogFont) )
			{
				if ( m_Font.DeleteObject() )
				{
					m_Font.CreateFontIndirect(&LogFont);
				}
			}
		}

		const int GetVAdj() const
		{
			return m_nVerticalAdj;
		}

		void SetVAdj(int vDiff)
		{
			m_nVerticalAdj = vDiff;
		}

	private:
		UINT m_nDlgItem;
		HWND m_hwndParent;
		CFontEx m_Font;
		int m_nVerticalAdj;
	};


	class CBMPButton : public CBMPButtonImpl<CBMPButton>
	{
	public:
		DECLARE_WND_SUPERCLASS(_T("WTL_BitmapButton"), GetWndClassName())

		CBMPButton(DWORD dwExtendedStyle = BMPBTN_AUTOSIZE, HIMAGELIST hImageList = NULL) :
				CBMPButtonImpl<CBMPButton>(dwExtendedStyle, hImageList)
		{ }
	};

};

#pragma warning(default : 4996)

#endif //__CEBMPBUTTON_H__
